import { CanActivate, ExecutionContext } from '@nestjs/common';
import { InjectConnection } from '@nestjs/mongoose';
import { Connection } from 'mongoose';
import { Reflector } from '@nestjs/core';
import { Request } from 'express';
import { difference } from 'lodash/fp';
import type { Actions, Subjects } from '@/casl/casl-ability.factory';
import { CaslAbilityFactory } from '@/casl/casl-ability.factory';
import {
  IS_CHECK_PERMISSION_KEY,
  CheckPermissionMeta,
} from '@/common/decorators/current-user.decorator';
import { ValidationException } from '../exceptions/custom-exception';
import { subject } from '@casl/ability';

const caslMethodMapping: Record<string, Actions> = {
  POST: 'create',
  GET: 'read',
  PATCH: 'update',
  DELETE: 'delete',
};

export class CheckPermissionGuard implements CanActivate {
  constructor(
    private readonly reflector: Reflector,
    private readonly caslAbilityFactory: CaslAbilityFactory,
    @InjectConnection() private readonly connection: Connection,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const meta = this.reflector.get<CheckPermissionMeta>(
      IS_CHECK_PERMISSION_KEY,
      context.getHandler(),
    );
    if (!meta) return true;

    const {
      modelName,
      errorType,
      options: {
        action: customAction,
        key: optionKey,
        value: { type: optionValueType, valueKey: optionValueKey },
      },
    } = meta;

    const mongooseModelName =
      typeof modelName === 'string' ? modelName : modelName.mongoose;
    const caslModelName =
      typeof modelName === 'string' ? modelName : modelName.casl;
    const request = context.switchToHttp().getRequest<Request>();
    const user = request.user;
    const method = request.method;
    // const id =
    //   request.params?.id || request.body?.workId || request.query?.workId;
    const id =
      request.params?.id || request?.[optionValueType]?.[optionValueKey];
    const action = customAction || caslMethodMapping[method];

    let permission = false;
    // 字段权限
    let keyPermission = true;
    const ability = this.caslAbilityFactory.defineRoles(user);
    const rule = ability.relevantRuleFor(action, caslModelName as Subjects);

    if (rule && rule.conditions) {
      const model = this.connection.model(mongooseModelName);
      const record = await model.findOne({ [optionKey]: id }).lean();
      permission = ability.can(action, subject(caslModelName, record) as any);
    } else {
      permission = ability.can(action, caslModelName as Subjects);
    }

    if (rule && rule.fields) {
      const fields = rule.fields;
      const paylocdkeys = Object.keys(request.body);
      keyPermission = difference(paylocdkeys, fields).length === 0;
    }

    if (!user || !id) throw new ValidationException(errorType);

    if (!permission || !keyPermission) {
      throw new ValidationException(errorType);
    }

    return true;
  }
}
